/*
 * TowerBase.java
 *
 * Created on November 24, 2007, 3:19 PM
 *
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package hextd.tower;

import hextd.map.JsonTower;
import hextd.Constants;
import hextd.hex.HexTile;
import hextd.creep.HexTDCreep;
import hextd.util.Cache;
import hextd.util.ParameterUtils;
import hextd.util.TowerChangeListener;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Point2D.Double;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.usoog.tdcore.TDCore;
import com.usoog.tdcore.exception.InsufficientResourcesException;
import com.usoog.tdcore.exception.TileAlreadyHasTowerException;
import com.usoog.tdcore.exception.TileHasNoTowerException;
import com.usoog.tdcore.exception.TileNotBuildableException;
import com.usoog.tdcore.gamegrid.GridPoint;
import com.usoog.tdcore.gamegrid.ScaledCanvas;
import com.usoog.tdcore.gamestate.GameState;
import com.usoog.tdcore.player.Player;
import com.usoog.tdcore.player.Resources;
import com.usoog.tdcore.tile.Tile;
import hextd.core.GameStateImplementation;

/**
 *
 * @author hylke
 */
public abstract class TowerBase implements HexTDTower {

	private Player owner;
	protected double paintScale, offsetX, offsetY;
	protected float strokeScale;
	protected GameStateImplementation gameState;
	protected ScaledCanvas canvas;
	private FactoryTower.type type;
	private TowerFireControl fc;
	protected ArrayList<TowerUpgradeDamage> upgradeDamage;
	protected ArrayList<TowerUpgradeRange> upgradeRange;
	private ArrayList<TowerChangeListener> towerChangeListeners;
	//
	protected int targetingMode = 0;
	protected String[] modes = {"Close", "Hard", "Weak", "Furthest"};
	protected boolean retarget = false;
	protected boolean lock = true;
	//
	protected HexTile hex;
	protected GridPoint hexLocation = new GridPoint(0, 0);
	protected int hexInfluence = 2;
	protected Point2D.Double gameLocation = new Point2D.Double();
	protected Point2D.Double paintLocation = new Point2D.Double();
	protected int level = 0;
	protected int levelMax = 9;
	protected int levelInQue = 0;
	protected double rangeBase = 0;
	protected double rangeCurrent = 0;
	protected double rangeCurrent2 = 0;
	protected double rangePaint = 0;
	protected int damageBase = 0;
	protected long damageCurrent = 0;
	protected String damageText = "";
	protected String rangeText = "";
	//
	public static DecimalFormat form = new DecimalFormat("#,###.##");
	protected PanelTowerSettings pts;
	//
	protected Color lineColor;
	protected Color rangeColor = Constants.colorRangeCircle;
	protected Shape[] shapes;
	protected int[] shapeAction;
	protected Color[] shapeColor;
	protected Stroke[] strokes;
	protected Shape rangeCircle;
	protected Stroke rangeStroke;
	protected Font levelFont;
	protected float[] levelLocation; // Location of the level number
	protected AffineTransform atTranslate = new AffineTransform();
	private AffineTransform atCorrect;
	private boolean bodyUsesSVG;
	private String bodySVGName;
	private double bodyScale;
	//
	protected boolean selected = false;
	protected boolean selling = false;
	private boolean isBeingSold = false;
	private Map<String, Long> price;
	//
	protected boolean active = false;

	protected class TowerAction {

		public int gameTime;
		public String action;

		public TowerAction(int gameTime, String action) {
			this.gameTime = gameTime;
			this.action = action;
		}
	}

	protected class TowerActionMode extends TowerAction {

		public String mode;
		public String value;

		public TowerActionMode(int gameTime, String mode, String value) {
			super(gameTime, "mode");
			this.mode = mode;
			this.value = value;
		}
	}

	public enum ModeType {

		Lock,
		Targeting,
		Group
	}
	//
	private ArrayList<TowerAction> actionQueue;
	private Shape[] queueShapes;
	public static Color halfWhite = new Color(255, 255, 255, 64);
	private static Comparator<HexTDCreep> compareFast = new CreepCompare.CompareFast();
	private static Comparator<HexTDCreep> compareHard = new CreepCompare.CompareHard();
	private static Comparator<HexTDCreep> compareWeak = new CreepCompare.CompareWeak();
	private static Comparator<HexTDCreep> compareFar = new CreepCompare.CompareFurthest();

	/**
	 * Creates a new instance of TowerBase
	 */
	public TowerBase(FactoryTower.type t, Map<String, Long> price, int damage, double range) {
		this.price = price;
		type = t;
		damageBase = damage;
		rangeBase = range;
		levelLocation = new float[2];
		upgradeDamage = new ArrayList<TowerUpgradeDamage>();
		upgradeRange = new ArrayList<TowerUpgradeRange>();
		towerChangeListeners = new ArrayList<TowerChangeListener>();
		actionQueue = new ArrayList<TowerBase.TowerAction>();
	}

	public void init(
			GameStateImplementation gs,
			HexTile hex,
			Player owner,
			int activateTick)
			throws TileAlreadyHasTowerException, TileNotBuildableException {
		actionQueue.add(new TowerAction(activateTick, "activate"));
		gameState = gs;
		damageCurrent = damageBase;
		this.owner = owner;
		this.hex = hex;
		if (hex != null) {
			hexLocation = hex.getGridLocation();
			hex.placeTower(this);
		}

		atCorrect = new AffineTransform();
		setLocation();
		calcDamageRange();
	}

	@Override
	public final void doTick(int gameTime) {
		checkQueue(gameTime);
		if (active) {
			runTick(gameTime);
		}
	}

	@Override
	public boolean isActive() {
		return active;
	}

	abstract protected void runTick(int gameTime);

	protected void addActionItem(TowerAction item) {
		actionQueue.add(item);
	}

	protected void checkQueue(int gameTime) {
		for (Iterator<TowerAction> i = actionQueue.iterator(); i.hasNext();) {
			TowerAction a = i.next();
			if (a.gameTime == gameTime) {
				handleAction(a);
				i.remove();
			}
		}
	}

	protected void handleAction(TowerAction a) {
		if (a.action.equals("activate")) {
			runActivate();
			return;
		}
		if (a.action.equals("upgrade")) {
			runUpgrade();
			return;
		}
		if (a.action.equals("mode")) {
			TowerActionMode am = (TowerActionMode) a;
			activateSetMode(am.mode, am.value);
			return;
		}
		if (a.action.equals("sell")) {
			runSell();
			return;
		}
		System.err.println("TowerBase::handleAction: Unknown action: " + a.action + "!");
	}

	protected boolean runActivate() {
		Player player = getOwner();
		if (player.getResources().doPay(price)) {
			hex.addClaim(owner, level + 1, hexInfluence);
			active = true;
			return true;
		} else {
			System.err.println("TowerBase::handleAction newTower: ERROR! " + gameState.getGameTime().getTime() + " User " + owner
					+ " can not activate new tower of type " + type.toString()
					+ " at " + hexLocation.col + "," + hexLocation.row);
			try {
				hex.deleteTower();
			} catch (TileHasNoTowerException ex) {
				Logger.getLogger(TowerBase.class.getName()).log(Level.SEVERE, null, ex);
			}
			delete();
			gameState.removeTower(this);
			return false;
		}
	}

	@Override
	public Player getOwner() {
		return owner;
	}

	@Override
	public void setMode(int activateTick, String m, String v) {
		actionQueue.add(new TowerActionMode(activateTick, m, v));
	}

	protected boolean activateSetMode(String m, String v) {
		int val;
		ModeType modeType = ModeType.valueOf(m);
		switch (modeType) {
			case Lock:
				lock = (v.equals("1"));
				val = lock ? 1 : 0;
				if (pts != null && pts.getMode(modeType) != val) {
					pts.setMode(modeType, val, true);
				}
				return true;
			case Targeting:
				val = Integer.parseInt(v);
				targetingMode = ParameterUtils.forceIndexBounds(modes, val);
				retarget = true;
				if (pts != null && pts.getMode(modeType) != targetingMode) {
					pts.setMode(modeType, targetingMode, true);
				}
				return true;
			default:
				return false;
		}
	}

	@Override
	public String getMode(String m) {
		ModeType valueOf = ModeType.valueOf(m);
		switch (valueOf) {
			case Lock:
				if (lock) {
					return "1";
				} else {
					return "0";
				}
			case Targeting:
				return "" + targetingMode;
			default:
				return "-1";
		}
	}

	@Override
	public double getRangeBase() {
		return rangeBase;
	}

	@Override
	public double getRangeCurrent() {
		return rangeCurrent;
	}

	@Override
	public Map<String, Long> getUpgradeCost(String mode, boolean afterQueue, Map<String, Long> target) {
		if (target == null) {
			target = new HashMap<String, Long>();
		} else {
			target.clear();
		}

		int targetLevel = level;
		if (afterQueue) {
			targetLevel += levelInQue;
		}

		int numLevels = 1;
		if (mode.equalsIgnoreCase("full")) {
			numLevels = levelMax - targetLevel;
		}
		if (targetLevel < levelMax) {
			for (Entry<String, Long> entry : price.entrySet()) {
				target.put(entry.getKey(), numLevels * entry.getValue() / 2);
			}
		}

		return target;

	}

	@Override
	public boolean canUpgrade(String mode, boolean afterQueue) {
		if (afterQueue) {
			return (level + levelInQue < levelMax);
		} else {
			return (level < levelMax);
		}
	}

	@Override
	public void upgrade(int activateTick, String mode) throws InsufficientResourcesException {
		Map<String, Long> upgradeCost = getUpgradeCost(mode, true, null);
		Resources resources = owner.getResources();
		if (resources.queuePayment(upgradeCost)) {
			if (mode.equalsIgnoreCase("full")) {
				while (level + levelInQue < levelMax) {
					actionQueue.add(new TowerAction(activateTick, "upgrade"));
					levelInQue++;
				}
			} else {
				actionQueue.add(new TowerAction(activateTick, "upgrade"));
				levelInQue++;
			}
		} else {
			throw new InsufficientResourcesException("not enough resources to upgrade tower");
		}
	}

	protected void runUpgrade() {
		if (owner.getResources().doPay(getUpgradeCost("", false, null))) {
			level++;
			levelInQue--;
			damageCurrent = (int) (damageBase * (float) Math.pow(1.45, level));
			hex.addClaim(owner, 1, hexInfluence);
			calcDamageRange();
			fireTowerChangedEvent();
		} else {
			TDCore.getLogger().logp(Level.SEVERE, getClass().getName(), "runUpgrade", "Not enough resources to upgrade tower at col=" + hexLocation.col + " row=" + hexLocation.row);
		}
	}

	@Override
	public Map<String, Long> getSellPrice(Map<String, Long> target) {
		if (target == null) {
			target = new HashMap<String, Long>();
		} else {
			target.clear();
		}
		for (Entry<String, Long> entry : price.entrySet()) {
			target.put(entry.getKey(), Math.round(0.75 * (1.0 + 0.5 * level) * entry.getValue()));
		}
		return target;
	}

	@Override
	public boolean canBeSold() {
		return !isBeingSold;
	}

	@Override
	public void sell(int activateTick) {
		if (!isBeingSold) {
			isBeingSold = true;
			actionQueue.add(new TowerAction(activateTick, "sell"));
		}
	}

	private void runSell() {

		owner.getResources().doReceive(getSellPrice(null));
		try {
			hex.deleteTower();
		} catch (TileHasNoTowerException ex) {
			TDCore.getLogger().log(Level.SEVERE, null, ex);
		}
		hex.addClaim(owner, -level - 1, hexInfluence);
		delete();
		gameState.removeTower(this);
	}

	@Override
	public void cancelBuild() {
		if (!active) {
			selling = true;

			owner.getResources().unQueuePayment(price);
			Map<String, Long> temp = new HashMap<String, Long>();
			while (levelInQue > 0) {
				levelInQue--;
				owner.getResources().unQueuePayment(getUpgradeCost("", true, temp));
			}
			try {
				hex.deleteTower();
			} catch (TileHasNoTowerException ex) {
				TDCore.getLogger().log(Level.SEVERE, null, ex);
			}
			delete();
			gameState.removeTower(this);
		}
	}

	protected void calcDamageRange() {
		int fcCount = 0;
		if (fc != null) {
			fcCount = 1;
		}
		double lvl = Math.pow(1.45, level);
		double upg = (1f
				+ TowerUpgradeDamage.upgradeEffectDamage * upgradeDamage.size()
				+ TowerFireControl.upgradeEffectDamage * fcCount);
		damageCurrent = Math.round(damageBase * lvl * upg);
		damageText = ""
				+ TowerBase.form.format(damageBase) + "*"
				+ TowerBase.form.format(lvl) + "*"
				+ TowerBase.form.format(upg) + "="
				+ TowerBase.form.format(damageCurrent);

		lvl = (float) Math.pow(1.07, level);
		upg = (1f
				+ TowerUpgradeRange.upgradeEffectRange * upgradeRange.size()
				+ TowerFireControl.upgradeEffectRange * fcCount);
		rangeCurrent = (float) (rangeBase * lvl * upg);
		rangeText = ""
				+ TowerBase.form.format(rangeBase) + "*"
				+ TowerBase.form.format(lvl) + "*"
				+ TowerBase.form.format(upg) + "="
				+ TowerBase.form.format(rangeCurrent);

		rangePaint = rangeCurrent * paintScale;
		rangeCurrent2 = rangeCurrent * rangeCurrent;
		rangeCircle = new Ellipse2D.Double(-rangePaint, -rangePaint, rangePaint * 2, rangePaint * 2);
	}

	abstract protected void generateShapes();

	public String getSvgName() {
		return null;
	}

	@Override
	public void setSelected(boolean selected) {
		this.selected = selected;
	}

	/* Non specific enemy finders findEnemiesInRange and findEnemyClosest
	 * There are used for bouncing projectiles and splash damage
	 * These can not be deferred to a firecontrol tower.
	 */
	protected HexTDCreep[] findEnemiesInRange(double x, double y, double range) {
		List<HexTDCreep> tempEnemies = new ArrayList<HexTDCreep>();
		Collection<HexTDCreep> targets = gameState.getTargets(owner);

		double r2 = range * range;
		double dx, dy, d2;
		Double targetLocation = new Double();

		for (HexTDCreep e : targets) {
			if (e.isValidTarget()) {
				e.getGameLocation(targetLocation);
				dx = targetLocation.x - x;
				dy = targetLocation.y - y;
				d2 = dx * dx + dy * dy;
				if (d2 < r2) {
					tempEnemies.add(e);
				}
			}
		}
		HexTDCreep[] retVal = new HexTDCreep[tempEnemies.size()];
		retVal = tempEnemies.toArray(retVal);
		return retVal;
	}

	protected HexTDCreep findEnemyClosest(double x, double y, double range, HexTDCreep[] not) {
		Collection<HexTDCreep> targets = gameState.getTargets(this.owner);
		HexTDCreep foundEnemy = null;
		double distance2Min = range * range;
		double distance2;
		double dx, dy;
		Double targetLocation = new Double();

		boolean found;
		for (HexTDCreep e : targets) {
			found = false;
			for (int n = 0; n < not.length; n++) {
				if (not[n] == e) {
					found = true;
					continue;
				}
			}
			if (!found && e.isValidTarget()) {
				e.getGameLocation(targetLocation);
				dx = targetLocation.x - x;
				dy = targetLocation.y - y;
				distance2 = dx * dx + dy * dy;
				if (distance2 < distance2Min) {
					distance2Min = distance2;
					foundEnemy = e;
				} else if (distance2 == distance2Min) {
					if (compareWeak.compare((HexTDCreep) e, (HexTDCreep) foundEnemy) > 0) {
						distance2Min = distance2;
						foundEnemy = e;
					}
				}
			}
		}
		return foundEnemy;
	}

	/* Specific enemy finders
	 * These can not be deferred to a firecontrol tower.
	 */
	protected HexTDCreep[] findEnemiesFastest(double x, double y, double range, int number, boolean exShielded) {
		return findEnemiesFastest(x, y, range, number, new HexTDCreep[0], exShielded);
	}

	protected HexTDCreep[] findEnemiesFastest(double x, double y, double range, HexTDCreep[] preferred, int number, boolean exShielded) {
		List<HexTDCreep> found = new ArrayList<HexTDCreep>(number);
		double r2 = range * range;
		double dx, dy, d2;
		HexTDCreep pe;
		Double targetLocation = new Double();

		if (preferred != null) {
			for (int p = 0; p < preferred.length; p++) {
				pe = preferred[p];
				if (pe.isValidTarget()) {
					pe.getGameLocation(targetLocation);
					dx = targetLocation.x - x;
					dy = targetLocation.y - y;
					d2 = dx * dx + dy * dy;
					if (d2 < r2) {
						found.add(pe);
					}
				}
			}
		}
		HexTDCreep[] not = new HexTDCreep[found.size()];
		not = found.toArray(not);
		if (number - not.length > 0) {
			HexTDCreep[] extra = findEnemiesFastest(x, y, range, number - not.length, not, exShielded);
			found.addAll(Arrays.asList(extra));
		}
		HexTDCreep[] retval = new HexTDCreep[found.size()];
		return found.toArray(retval);
	}

	protected HexTDCreep[] findEnemiesFastest(double x, double y, double range, int number, HexTDCreep[] not, boolean exShielded) {
		return CreepCompare.findLargestSet(
				gameState.getTargets(owner),
				number,
				x, y, range,
				compareFast,
				not,
				exShielded);
	}

	/* Specific enemy finders
	 * These can be deferred to a firecontrol tower.
	 */
	public HexTDCreep findEnemy(HexTDCreep preferedEnemy, boolean exShielded) {
		if (retarget) {
			preferedEnemy = null;
			retarget = false;
		}
		if (fc != null) {
			return fc.findEnemy(this, preferedEnemy, exShielded);
		}

		switch (targetingMode) {
			case 0:
				return findEnemyClosest(preferedEnemy, exShielded);
			case 1:
				return findEnemyHardest(preferedEnemy, exShielded);
			case 2:
				return findEnemyWeakest(preferedEnemy, exShielded);
			case 3:
				return findEnemyFurthest(preferedEnemy, exShielded);
			default:
				System.err.println("TowerBase::FindEnemy: Unknown mode " + targetingMode);
		}
		return null;
	}

	private HexTDCreep findEnemyClosest(HexTDCreep preferedEnemy, boolean exShielded) {
		Collection<HexTDCreep> targets = gameState.getTargets(owner);
		HexTDCreep foundEnemy = null;
		double distance2Min = 1E10f;
		double distance2;
		double dx, dy;
		Double targetLocation = new Double();

		if (preferedEnemy != null) {
			if (preferedEnemy.isValidTarget() && !(exShielded && preferedEnemy.isShielded())) {
				preferedEnemy.getGameLocation(targetLocation);
				dx = targetLocation.x - gameLocation.x;
				dy = targetLocation.y - gameLocation.y;
				distance2 = dx * dx + dy * dy;
				if (distance2 < rangeCurrent2) {
					return preferedEnemy;
				}
			}
		}
		for (HexTDCreep e : targets) {
			if (e.isValidTarget() && !(exShielded && e.isShielded())) {
				e.getGameLocation(targetLocation);
				dx = targetLocation.x - gameLocation.x;
				dy = targetLocation.y - gameLocation.y;
				distance2 = dx * dx + dy * dy;
				if (distance2 < rangeCurrent2 && distance2 < distance2Min) {
					distance2Min = distance2;
					foundEnemy = e;
				} else if (distance2 == distance2Min) {
					if (compareWeak.compare(e, foundEnemy) > 0) {
						distance2Min = distance2;
						foundEnemy = e;
					}
				}
			}
		}
		return foundEnemy;
	}

	private HexTDCreep findEnemyWeakest(HexTDCreep preferedEnemy, boolean exShielded) {
		return CreepCompare.findLargestSingle(gameState.getTargets(owner), gameLocation.x, gameLocation.y, rangeCurrent, compareWeak, preferedEnemy, exShielded);
	}

	private HexTDCreep findEnemyHardest(HexTDCreep preferedEnemy, boolean exShielded) {
		return CreepCompare.findLargestSingle(gameState.getTargets(owner), gameLocation.x, gameLocation.y, rangeCurrent, compareHard, preferedEnemy, exShielded);
	}

	private HexTDCreep findEnemyFurthest(HexTDCreep preferedEnemy, boolean exShielded) {
		return CreepCompare.findLargestSingle(gameState.getTargets(owner), gameLocation.x, gameLocation.y, rangeCurrent, compareFar, preferedEnemy, exShielded);
	}

	private void setLocation() {
		if (hex != null) {
			hex.getGameLocation(gameLocation);
		}
	}

	@Override
	public void resetScale(ScaledCanvas sc) {
		canvas = sc;
		if (canvas != null) {
			paintScale = canvas.getPaintScale();
			strokeScale = canvas.getDefaultStrokeWidth();
			offsetX = canvas.getOffsetX();
			offsetY = canvas.getOffsetY();
			canvas.fromGameToPaintCoordinates(gameLocation, paintLocation);

			calcDamageRange();
			generateShapes();

			double max = GameState.actionDelay;
			queueShapes = new Shape[GameState.actionDelay * 2];
			for (int i = 0; i < queueShapes.length; i++) {
				queueShapes[i] = new Arc2D.Double(-paintScale, -paintScale, paintScale * 2, paintScale * 2, 0, 360 * i / (max - 1), Arc2D.OPEN);
			}

			atTranslate.setToIdentity();
			atTranslate.translate(paintLocation.x, paintLocation.y);

			rangeStroke = new BasicStroke(1.0f * strokeScale, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);

			levelFont = new Font(Font.DIALOG, Font.PLAIN, (int) (0.40 * paintScale));
			levelLocation[0] = -0.95f * (float) HexTile.relativeHexWidth;
			levelLocation[1] = 0.5f * (float) paintScale;

			bodySVGName = getSvgName();
			if (bodySVGName != null) {
				bodyUsesSVG = true;
				bodyScale = paintScale * 1.5f;
				atCorrect.setToIdentity();
				atCorrect.setToTranslation(-bodyScale / 2, -bodyScale / 2);
			}
		}
	}

	public void paintQueue(Graphics2D g2, int gameTime) {
		g2.setColor(rangeColor);
		g2.setStroke(rangeStroke);
		for (TowerAction a : actionQueue) {
			int delta = a.gameTime - gameTime;
			if (delta >= 0 && delta < queueShapes.length) {
				g2.draw(queueShapes[delta]);
			}
		}
	}

	@Override
	public void paint(Graphics2D g2, int gameTime) {
		if (canvas == null) {
			System.err.println("TowerBase::paint: Painting without normal context!");
		}
		Stroke defaultStroke = g2.getStroke();
		AffineTransform saveXform = g2.getTransform();

		g2.transform(atTranslate);
		g2.setColor(lineColor);
		g2.setFont(levelFont);
		g2.drawString("" + (level + 1), levelLocation[0], levelLocation[1]);

		if (selected) {
			g2.setColor(rangeColor);
			g2.draw(rangeCircle);
		}

		if (bodyUsesSVG) {
			AffineTransform saveXform2 = g2.getTransform();
			g2.transform(atCorrect);
			Cache cache = Cache.getInstance();
			cache.renderSvgImage(bodySVGName, g2, (int) bodyScale, (int) bodyScale);
			g2.setTransform(saveXform2);
		} else {
			if (shapes[0] != null) {
				for (int i = 0; i < shapes.length; i++) {
					g2.setColor(shapeColor[i]);
					switch (shapeAction[i]) {
						case 0:
							g2.setStroke(strokes[i]);
							g2.fill(shapes[i]);
							g2.draw(shapes[i]);
							break;
						case 1:
							g2.setStroke(strokes[i]);
							g2.draw(shapes[i]);
							break;
						case 2:
							g2.fill(shapes[i]);
							break;
					}
				}
			}
		}
		paintQueue(g2, gameTime);

		g2.setTransform(saveXform);
		g2.setStroke(defaultStroke);

	}

	@Override
	public HexTile getTile() {
		return hex;
	}

	@Override
	public void setTile(Tile hex) {
		this.hex = (HexTile) hex;
	}

	@Override
	public void setSetting(JsonTower.optionKey key, String value) {
		switch (key) {
			case maxlevel:
				levelMax = Integer.parseInt(value);
				System.out.println("maxLevel set to " + levelMax);
				break;
			default:
				System.err.println("TowerHome::setSetting: Unknown key: " + key);
				break;
		}
	}

	@Override
	public void registerTower(HexTDTower t) {
		if (t != this && !selling) {
			switch (t.getType()) {
				case firecontrol:
					if (type != FactoryTower.type.upgradeDamage && type != FactoryTower.type.upgradeRange && type != FactoryTower.type.firecontrol) {
						if (fc != null) {
							fc.removeClient(this);
						}
						fc = (TowerFireControl) t;
						fc.addClient(this);
						calcDamageRange();
					}
					break;
				case upgradeDamage:
					if (type != FactoryTower.type.upgradeDamage && type != FactoryTower.type.upgradeRange && type != FactoryTower.type.firecontrol) {
						TowerUpgradeDamage tud = (TowerUpgradeDamage) t;
						if (!upgradeDamage.contains(tud)) {
							upgradeDamage.add(tud);
							tud.addClient(this);
							calcDamageRange();
						}
					}
					break;
				case upgradeRange:
					TowerUpgradeRange tur = (TowerUpgradeRange) t;
					if (!upgradeRange.contains(tur)) {
						upgradeRange.add(tur);
						tur.addClient(this);
						calcDamageRange();
					}
					break;
			}
		}
	}

	@Override
	public void unregisterTower(HexTDTower t) {
		switch (t.getType()) {
			case firecontrol:
				if (fc != null) {
					fc.removeClient(this);
					fc = null;
				}
				break;
			case upgradeDamage:
				TowerUpgradeDamage tud = (TowerUpgradeDamage) t;
				upgradeDamage.remove(tud);
				tud.removeClient(this);
				break;
			case upgradeRange:
				TowerUpgradeRange tur = (TowerUpgradeRange) t;
				upgradeRange.remove(tur);
				tur.removeClient(this);
				break;
		}
		calcDamageRange();
	}

	@Override
	public void delete() {
		selling = true;
		if (fc != null) {
			fc.removeClient(this);
			fc = null;
		}
		int i;
		for (i = upgradeDamage.size() - 1; i >= 0; i--) {
			TowerUpgradeDamage tud = upgradeDamage.remove(i);
			tud.removeClient(this);
		}
		for (i = upgradeRange.size() - 1; i >= 0; i--) {
			TowerUpgradeRange tur = upgradeRange.remove(i);
			tur.removeClient(this);
		}
	}

	@Override
	public FactoryTower.type getType() {
		return type;
	}

	public String getKey() {
		return type.code;
	}

	public String getName() {
		return type.name;
	}

	protected void makeButtons() {
		if (canvas != null) {
			PanelTowerSettingsSingle p = new PanelTowerSettingsSingle(this, gameState.getGameManager());
			p.setModes(ModeType.Targeting, modes);
			p.setMode(ModeType.Targeting, targetingMode, true);
			pts = p;
		}
	}

	@Override
	public PanelTowerSettings getButtons() {
		if (pts == null) {
			makeButtons();
		}
		return pts;
	}

	@Override
	public void addTowerChangeListener(TowerChangeListener l) {
		towerChangeListeners.add(l);
	}

	@Override
	public void removeTowerChangeListener(TowerChangeListener l) {
		towerChangeListeners.remove(l);
	}

	protected void fireTowerChangedEvent() {
		for (int i = 0; i < towerChangeListeners.size(); i++) {
			towerChangeListeners.get(i).towerChanged();
		}
	}
}
